﻿using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using Utility.Math;
using System;
using System.Linq;
using System.Reflection;

#if UNITY_EDITOR
namespace Utility.Inspector
{
	public static class EditorGUIEX
	{
		static float H_Line => EditorGUIUtility.singleLineHeight;
		static float W_Label => EditorGUIUtility.labelWidth;
		public static GUIContent ToGUIContent (this string str)
		{
			return str == null || string.IsNullOrWhiteSpace (str) ? null : new GUIContent (str);
		}

		public static bool NeedDraw (this GUIContent label)
		{
			return label != null && !string.IsNullOrWhiteSpace (label.text);
		}

		#region resources
		static Texture2D _icon_refresh;
		public static Texture2D Icon_Refresh => _icon_refresh ?? (_icon_refresh = (Texture2D)EditorGUIUtility.Load ("RotateTool"));

		#endregion


		#region string popup
		public static string StringPopupField (Rect position, GUIContent label, string value, string[] values, string[] showNames)
		{
			if (values != null && values.Length > 0)
			{
				Rect pos_field = position.ShrinkRight (15);
				Rect pos_btn = position.DockRight (16);
				//field
				int oldIndex = Array.FindIndex (values, s => s == value);
				string showName = EditorGUI.TextField (pos_field, label, showNames.TryGet (oldIndex, out var n) ? n : "");
				string val = string.IsNullOrEmpty (showName) ? null : value;
				//btn
				using (IndentLevelScope.Zero ())
				{
					int newIndex = EditorGUI.Popup (pos_btn, -1, showNames, EditorStyles.popup);
					if (newIndex >= 0)
					{
						val = values[newIndex];
					}
				}
				return val;
			}
			else
			{
				return EditorGUI.TextField (position, label, value);
			}
		}

		public static string StringPopupField (Rect position, string label, string value, string[] values, string[] showNames)
		{
			return StringPopupField (position, new GUIContent (label), value, values, showNames);
		}

		public static string StringPopupField (Rect position, GUIContent label, string value, string[] values, bool editable = false)
		{
			if (values != null && values.Length > 0)
			{
				Rect pos_field = position.ShrinkRight (15);
				Rect pos_btn = position.DockRight (16);
				//field
				string val = EditorGUI.TextField (pos_field, label, value);
				val = editable || string.IsNullOrEmpty (val) ? val : value;
				//btn
				using (IndentLevelScope.Zero ())
				{
					int index = EditorGUI.Popup (pos_btn, -1, values, EditorStyles.popup);
					if (index >= 0)
					{
						val = values[index];
					}
				}
				return val;
			}
			else
			{
				return EditorGUI.TextField (position, label, value);
			}
		}

		public static string StringPopupField (Rect position, string label, string value, string[] values, bool editable = false)
		{
			return StringPopupField (position, new GUIContent (label), value, values, editable);
		}
		#endregion
		#region object popup
		public static T ObjectPopupField<T> (Rect position, string label, T value, IList<T> values, Func<T, string> nameGetter = null)
		{
			if ((values?.Count ?? 0) == 0)
			{
				EditorGUI.LabelField (position, label, "No valid item");
				return default;
			}

			var names = (nameGetter == null ? values.Select (t => t.ToString ()) : values.Select (t => nameGetter (t))).ToArray ();

			int oldIndex = values.FindIndex (value);
			int newIndex = EditorGUI.Popup (position, label, oldIndex, names);

			return values.TryGet (newIndex);
		}

		public static T ObjectPopupField<T> (Rect position, string label, T value, IDictionary<T, string> itemShowNameDict)
		{
			if ((itemShowNameDict?.Count ?? 0) == 0)
			{
				EditorGUI.LabelField (position, label, "No valid item");
				return default;
			}

			var items = itemShowNameDict.Keys.ToArray ();
			var names = itemShowNameDict.Values.ToArray ();

			int oldIndex = items.FindIndex (value);
			int newIndex = EditorGUI.Popup (position, label, oldIndex, names);

			return items.TryGet (newIndex);
		}
		#endregion
		#region object field
		public static T ObjectField<T> (Rect position, GUIContent label, T obj, bool allowSceneObjects)
			where T : UnityEngine.Object
		{
			obj = EditorGUI.ObjectField (position, label, obj, typeof (T), allowSceneObjects) as T;
			return obj;
		}

		public static T ObjectField<T> (Rect position, string label, T obj, bool allowSceneObjects)
			where T : UnityEngine.Object
		{
			obj = EditorGUI.ObjectField (position, label, obj, typeof (T), allowSceneObjects) as T;
			return obj;
		}
		#endregion
		#region custom object field
		//custom button
		public static T CustomObjectField<T> (Rect position, GUIContent label, T obj, bool allowSceneObjects, out bool btnClicked)
			where T : UnityEngine.Object
		{
			//button
			var rect_btn = position.DockRight (20);
			btnClicked = GUI.Button (rect_btn, GUIContent.none, GUIStyle.none);

			obj = EditorGUI.ObjectField (position, label, obj, typeof (T), allowSceneObjects) as T;
			return obj;
		}

		public static T CustomObjectField<T> (Rect position, string label, T obj, bool allowSceneObjects, out bool btnClicked)
			where T : UnityEngine.Object
		{
			return CustomObjectField<T> (position, new GUIContent (label), obj, allowSceneObjects, out btnClicked);
		}

		//custom object picker
		public static T CustomObjectField<T> (Rect position, GUIContent label, T obj, bool allowSceneObjects, string searchFilter)
			where T : UnityEngine.Object
		{
			int controlId = GUIUtility.GetControlID (FocusType.Passive);
			bool btnClicked;
			obj = CustomObjectField (position, label, obj, allowSceneObjects, out btnClicked);

			if (btnClicked)
			{
				EditorGUIUtility.ShowObjectPicker<T> (obj, allowSceneObjects, searchFilter, controlId);
			}

			if (Event.current.commandName == "ObjectSelectorUpdated")
			{
				if (EditorGUIUtility.GetObjectPickerControlID () == controlId)
				{
					obj = EditorGUIUtility.GetObjectPickerObject () as T;
				}
			}
			return obj;
		}

		public static T CustomObjectField<T> (Rect position, string label, T obj, bool allowSceneObjects, string searchFilter)
			where T : UnityEngine.Object
		{
			return CustomObjectField<T> (position, new GUIContent (label), obj, allowSceneObjects, searchFilter);
		}
		#endregion
		#region nullable
		//float
		public static float? NullableFloatField (Rect position, GUIContent label, float? value)
		{
			float w_toggle = EditorGUIUtility.singleLineHeight;
			float w_label = label == null ? 0 : EditorGUIUtility.labelWidth;

			Rect pos_label = position.DockLeft (w_label);
			Rect pos_toggle = position.ShrinkLeft (w_label).ResizeWidth (w_toggle);
			Rect pos_field = position.ShrinkLeft (w_label + w_toggle);

			if (label != null)
			{
				EditorGUI.LabelField (pos_label, label);
			}

			bool hasValue = EditorGUI.Toggle (pos_toggle, value.HasValue);
			if (hasValue)
			{
				value = EditorGUI.FloatField (pos_field, value.GetValueOrDefault ());
			}
			else
			{
				string input = EditorGUI.TextField (pos_field, "null");
				value = float.TryParse (input, out var val) ? val : default (float?);
			}

			return value;
		}

		public static float? NullableFloatField (Rect position, string label, float? value)
		{
			return NullableFloatField (position, new GUIContent (label), value);
		}

		//int
		public static int? NullableIntField (Rect position, GUIContent label, int? value)
		{
			float w_toggle = EditorGUIUtility.singleLineHeight;
			float w_label = label == null ? 0 : EditorGUIUtility.labelWidth;

			Rect pos_label = position.DockLeft (w_label);
			Rect pos_toggle = position.ShrinkLeft (w_label).ResizeWidth (w_toggle);
			Rect pos_field = position.ShrinkLeft (w_label + w_toggle);

			if (label != null)
			{
				EditorGUI.LabelField (pos_label, label);
			}

			bool hasValue = EditorGUI.Toggle (pos_toggle, value.HasValue);
			if (hasValue)
			{
				value = EditorGUI.IntField (pos_field, value.GetValueOrDefault ());
			}
			else
			{
				string input = EditorGUI.TextField (pos_field, "null");
				value = int.TryParse (input, out var val) ? val : default (int?);
			}

			return value;
		}

		public static int? NullableIntField (Rect position, string label, int? value)
		{
			return NullableIntField (position, new GUIContent (label), value);
		}

		//bool
		public enum NullableBoolValues
		{
			Null, True, False
		}

		public static bool? NullableBoolField (Rect position, GUIContent label, bool? value)
		{
			NullableBoolValues enumValue = value.HasValue ? (value.Value ? NullableBoolValues.True : NullableBoolValues.False) : NullableBoolValues.Null;
			switch (EnumPopup (position, label, enumValue))
			{
				default:
				case NullableBoolValues.Null:
					return default;
				case NullableBoolValues.True:
					return true;
				case NullableBoolValues.False:
					return false;
			}
		}

		public static bool? NullableBoolField (Rect position, string label, bool? value)
		{
			return NullableBoolField (position, label.ToGUIContent (), value);
		}
		#endregion
		#region curve
		static Texture2D _tex_aaline;
		static Texture2D Tex_AALine
		{
			get
			{
				if (_tex_aaline == null)
				{
					_tex_aaline = Resources.Load<Texture2D> ("NodeGraph/AALine");
				}
				return _tex_aaline;
			}
		}

		public static void DrawBezierCurve (Vector2 from, Vector2 to, Vector2 fromTan, Vector2 toTan, Color color, float width, bool shadow)
		{
			Handles.DrawBezier (from, to, fromTan, toTan, color, Tex_AALine, width);
		}
		#endregion
		#region enum
		public static Enum EnumPopup (Rect position, GUIContent label, Enum value, bool ignoreShowName = false)
		{
			if (ignoreShowName)
			{
				return EditorGUI.EnumPopup (position, label, value);
			}
			else
			{
				float w_label = label.NeedDraw () ? W_Label : 0;
				if (w_label > 0)
				{
					EditorGUI.LabelField (position.ResizeWidth (w_label), label);
				}

				var items = EnumEX.GetValues (value.GetType ());
				var showNames = EnumEX.GetShowNames (value.GetType ());

				int oldIndex = Array.IndexOf (items, value);
				int newIndex = EditorGUI.Popup (position.ShrinkLeft (w_label), "", oldIndex, showNames);

				if (newIndex < 0 || newIndex >= items.GetLength (0))
				{
					newIndex = 0;
				}
				return (Enum)items.GetValue (newIndex);

			}
		}

		public static Enum EnumPopup (Rect position, string label, Enum value, bool ignoreShowName = false)
		{
			return EnumPopup (position, label.ToGUIContent (), value, ignoreShowName);
		}

		public static TEnum EnumPopup<TEnum> (Rect position, GUIContent label, TEnum value, bool ignoreShowName = false)
			where TEnum : Enum
		{
			return (TEnum)EnumPopup (position, label, (Enum)value, ignoreShowName);
		}

		public static TEnum EnumPopup<TEnum> (Rect position, string label, TEnum value, bool ignoreShowName = false)
			where TEnum : Enum
		{
			return EnumPopup (position, label.ToGUIContent (), value, ignoreShowName);
		}

		#endregion
		#region button
		public static bool RefreshButton (Rect position)
		{
			return GUI.Button (position, Icon_Refresh);
		}

		public static bool ButtonToggle (Rect position, GUIContent label, bool value)
		{
			return GUI.Toggle (position, value, label, "button");
		}

		public static bool ButtonToggle (Rect position, string label, bool value)
		{
			return ButtonToggle (position, label.ToGUIContent (), value);
		}
		#endregion
		#region decimal field
		public static decimal DecimalField (Rect position, string label, decimal value)
		{
			string str = EditorGUI.TextField (position, label, value.ToString ());
			return decimal.TryParse (str, out var newVal) ? newVal : value;
		}
		#endregion
	}
}
#endif